Skip to content

Update dependency agents to ^0.16.0#8

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/agents-0.x
Open

Update dependency agents to ^0.16.0#8
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/agents-0.x

Conversation

@renovate

@renovate renovate Bot commented Apr 19, 2026

Copy link
Copy Markdown

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Adoption Passing Confidence
agents (source) ^0.0.98^0.16.0 age adoption passing confidence

Release Notes

cloudflare/agents (agents)

v0.16.0

Compare Source

Minor Changes
  • #​1656 4c2d1a7 Thanks @​cjol! - Rebuild agents/browser on the codemode connector runtime (experimental).

    The browser tool surface is now a single durable tool, browser_execute: the model writes sandboxed code against a cdp connector (cdp.send, cdp.attachToTarget, cdp.spec, cdp.getDebugLog, …) instead of picking from several flat tools. Executions are recorded on a CodemodeRuntime Durable Object facet with abort-and-replay, so a run can pause for approval and resume with its browser session, tabs, and cookies intact.

    • BrowserConnector — a CodemodeConnector (name cdp) that owns CDP sockets keyed by execution id. Sockets are released at the end of every execution pass (onPassEnd); browser sessions are torn down on terminal status (disposeExecution) — never on pause.
    • Session modesone-shot (default, fresh session per execution), reuse (named shared session), and dynamic (starts one-shot; the model can promote with cdp.startSession() after e.g. logging in). Shared sessions are tracked in durable storage and survive hibernation; connector.sweep() reclaims expired ones from a scheduled task.
    • Safe sweeping — per-execution entries are touched on use and only swept after maxExecIdleMs (default 24h, matching the runtime's paused TTL), so a run awaiting approval keeps its browser. A swept entry leaves a tombstone so a later resume fails with a clear "expired or was swept" error instead of silently continuing in a fresh browser. Concurrent CDP calls share one in-flight socket connect instead of leaking the loser's WebSocket. Session-store locks wrap storage operations only — liveness probes and session create/delete happen outside the lock (with a commit re-check; a racing create's redundant session is deleted), so a hung Browser Rendering call can't serialize other session operations.
    • Stable attach handlescdp.attachToTarget returns { sessionId } where the id is a stable handle bound to the target (not a raw CDP session id), so handles recorded before a pause still work after the resume reconnects. The object shape mirrors the real Target.attachToTarget response, which is what models expect.
    • Model-actionable CDP errors — a "method wasn't found" failure on a send without a sessionId explains that page-scoped commands need cdp.attachToTarget first, and a missing targetId explains how to list/create targets.
    • createBrowserTools({ ctx, browser, loader, session? }) (AI SDK and TanStack AI variants) now requires the hosting Durable Object's ctx and returns { browser_execute }; createBrowserRuntime additionally exposes the runtime handle and connector for host-side wiring (approvals, sessionInfo/closeSession/sweep). The previous browser_search/flat-tool surface and createBrowserProvider are removed.
    • Worker entries must export the facet class: export { CodemodeRuntime } from "agents/browser".

    agents/chat gains pausedExecutionUpdate, a tool-part update that replaces a paused execution's output in the transcript with its resolved outcome (completed / rejected / paused again) — the transcript-side half of human-in-the-loop approvals for durable executions.

  • #​1746 e45b5ec Thanks @​threepointone! - Fix RPC calls hanging forever during connection churn (#​1738).

    useAgent's RPC layer now survives socket replacement. usePartySocket creates a brand-new socket whenever connection options change (async query refresh, enabled toggle, path change) — previously, a call issued against a stale agent reference was buffered inside the permanently-closed old socket and its promise never settled, and a call transmitted just before replacement lost its response with no rejection either.

    • agent.call() (and agent.stub / agent.setState) now route through the live socket, so stale references captured by mount-time effects keep working.
    • RPC requests are only handed to a socket once it's open. Until then they're queued by the hook and flushed on the next open — including on a replacement socket. This is safe: queued requests were never transmitted, so they can't double-execute.
    • Calls whose request was already transmitted are rejected with Connection closed when their socket closes or is replaced (the response is connection-bound and can never arrive). Calls in flight on a newer socket are no longer spuriously rejected by a stale close event from an old socket.
    • Queued calls only follow the connection to the same agent instance. If the hook is re-pointed at a different address (the agent, name, basePath, or path props change) before a queued call could be transmitted, the call is rejected instead of executing against an instance it wasn't composed for.
    • AgentClient similarly keeps buffered (untransmitted) calls pending across transient disconnects — PartySocket re-sends them on reconnect — and only rejects calls the server actually received.
    • Non-streaming calls now have a default 30s timeout as a backstop so lost responses reject instead of hanging. Configure per client via defaultCallTimeout (0 disables) on useAgent / AgentClient, or per call via the existing timeout option (timeout: 0 opts out). Streaming calls are exempt.
    • RPC responses that arrive with no matching pending call (e.g. after a timeout) now log a console.warn instead of being silently discarded.
Patch Changes
  • #​1742 4b201a9 Thanks @​threepointone! - Fix duplicated assistant text parts when a stream resume is replayed twice (#​1733).

    The server intentionally sends CF_AGENT_STREAM_RESUMING for the same request from both onConnect and its CF_AGENT_STREAM_RESUME_REQUEST handler. When both offers reached the useAgentChat fallback path (e.g. the transport's resume handshake had already timed out), the client ACKed both, the full chunk buffer was replayed twice into the same accumulator, and the streaming reply rendered as two stacked text blocks until refresh.

    • useAgentChat now fallback-ACKs a given resume offer at most once per socket (reset on close/reconnect). A repeated offer is still handed to a waiting transport resume handshake first, so a fallback-observed stream can become transport-owned. It also resets the matching trailing assistant message on every replayed non-continuation start, not only while the resume request id is still pending.
    • The shared broadcast stream state machine re-initializes its accumulator on a replayed start, making replay idempotent under any number of replays.
    • Replay frames now carry continuation: true for continuation streams (persisted in stream metadata and restored after hibernation), so a replayed continuation appends to the existing assistant message instead of being mistaken for a fresh turn.
  • #​1740 6c9de59 Thanks @​threepointone! - Defer one-shot scheduled callbacks (and chat-recovery give-ups) on platform transients instead of consuming them mid-deploy (#​1730).

    A mid-execution Durable Object code-update reset surfaces storage failures in two shapes: the verbatim reset/supersede messages (already deferred) and SqlError: SQL query failed: Network connection lost. — a wrapper that drops the CF retryable flag and dodges the reset matcher. The second shape burned the in-process retry budget inside the same few-seconds reset window (which outlasts the retry schedule by design) and then consumed the one-shot row on exhaustion, freezing the turn for minutes until incident re-detection — in the reported production capture, storage was healthy again 15 ms after the final attempt.

    • agents — new cause-aware isPlatformTransientError classifier (exported, alongside isDurableObjectCodeUpdateReset): reset/supersede messages, retryable-flagged platform errors (excluding overloaded), and "Network connection lost.", looked up through wrapper cause chains. _executeScheduleCallback keeps in-process retries for connection-lost transients (a genuine blip heals fast) but on exhaustion of a one-shot row it now re-throws instead of swallowing, so the row survives and the alarm re-runs it in the healthy window that follows. Genuine application errors are still abandoned after maxAttempts exactly as before.
    • @cloudflare/think_handleRecoveryCallbackError now defers (re-throws) on any platform transient instead of terminalizing through a give-up whose own seal needs the storage that is down; the bookkeeping write on the defer path is best-effort. The defer path no longer marks the recovered submission error (which made the deferred re-run skip with submission_not_running — a self-defeating defer); it stays running for the re-run to pick up. The give-up now seals the incident exhausted only after the terminal writes succeed, so a transient mid-seal defers the whole give-up for an idempotent re-run instead of half-sealing.
    • @cloudflare/ai-chat — same give-up seal ordering: the incident is sealed only after _exhaustChatRecovery (incl. the durable terminal record) succeeds, so a transient mid-seal preserves the one-shot row and the give-up re-runs in full on a healthy isolate.
  • #​1745 99c9326 Thanks @​cjol! - Make agent teardown reliable when the initiating request is already canceled (#​1625).

    The MCP Streamable-HTTP session-DELETE handler ran agent.destroy() via the request's ctx.waitUntil. By the time the DELETE lands the client is usually gone, the runtime gives a canceled request's trailing work little to no grace, and the multi-step teardown (drop tables, delete alarm, delete all storage, dispose connections) was routinely cut short — leaving half-deleted session DOs whose tables the constructor silently recreated on the next wake. (The associated waitUntil() tasks did not complete log warning itself originates inside workerd's WebSocket handling and is unaffected by this change.)

    Teardown is now deferred to the agent's own alarm invocation. The DELETE handler awaits two fast storage writes — a durable "condemned" marker plus an immediate alarm — and responds 204; the alarm then runs the real destroy() with a fresh execution budget. The marker is removed by the final deleteAll(), so it survives any interruption: alarm() checks it before any other work (including onStart) and finishes the teardown instead of resuming normal operation on a condemned agent, and _scheduleNextAlarm() keeps the destroy alarm armed rather than deleting it as "no work pending". destroy() itself now writes the marker first, so a direct destroy that gets interrupted converges the same way.

    New internal API: Agent._cf_scheduleDestroy() (used by the MCP handler; unlike destroy() it does not abort the isolate, so callers don't need to swallow an abort error). No public API or storage-schema changes; the marker is a single internal KV record (cf_agents_destroy_pending).

  • #​1729 1c8fdf5 Thanks @​threepointone! - Fix runFiber recovery starving when a recovery scan leaves work behind. _scheduleNextAlarm() only armed a follow-up alarm for active keepAlive leases, due schedules, and facet runs — never for orphaned cf_agents_runs rows (or interrupted/pending managed ledger fibers) still awaiting recovery. Because orphaned fibers hold no keepAlive ref, a scan that yielded on fiberRecoveryScanDeadlineMs (or a pass that retained a repeatedly-throwing unmanaged hook for retry) would never get another alarm, so the remaining fibers were never recovered. The scheduler now arms a follow-up alarm whenever fiber recovery work is still outstanding, so multi-pass recovery resumes and eventually drains every fiber (and ages out poison rows via fiberRecoveryMaxAgeMs).

    The follow-up alarm uses exponential backoff (capped at 5 minutes) while scans make no forward progress, so a repeatedly-throwing recovery hook — or a fiberRecoveryMaxAgeMs: 0 ("retain forever") row whose hook keeps throwing — no longer wakes the Durable Object every keepAliveIntervalMs. A scan that recovers any fiber (including a scan-deadline yield that drained part of a large batch) resets the backoff, so legitimate multi-pass draining stays prompt.

  • #​1737 bc43133 Thanks @​cjol! - Fix the two remaining #​1575 gaps in how in-band stream errors ({type: "error", errorText} chunks inside an otherwise-healthy provider stream) are observed after the fact.

    Errored-stream replay (partial content was lost on reconnect). A client reconnecting after an in-band error received the terminal error frame (#​1645) but not the content the model streamed before the error — the replay path only served status = 'completed' streams, so an errored stream's buffered chunks were unreachable, and the server pushes no messages on connect. ResumableStream gains replayErroredChunksByRequestId, and the resume-ACK terminal replay (_replayTerminalOnAck in both AIChatAgent and Think) now replays the errored stream's stored chunks before the done: true, error: true frame, so a reconnecting client observes the same sequence a live client did. No wire-format or schema changes: replayed chunks reuse the existing replay: true frame shape and the error text still comes from the durable terminal record.

    Agent-tool error attribution (cross-run contamination). When an in-band error frame was broadcast on a child agent and the active run was unknown, the error was stamped onto every tailed run — so an unrelated turn's failure (or one of several overlapping runs) could mark healthy runs as error, and capture depended on a tailer being attached at the right moment. Frames are now attributed by the request id they carry: each agent-tool run is bound to its turn's request id when the turn starts (persisted on the run row at start rather than at terminal, so attribution survives a DO restart mid-run), and only the owning run's error/progress state is updated. Frame inspection also no longer requires an attached tailer, so error capture is independent of tailer timing.

  • #​1707 d96a17c Thanks @​threepointone! - Fix keepAlive() leaving a stale 30s heartbeat alarm after the lease is released. Previously the dispose returned by keepAlive() (and used by keepAliveWhile()) only decremented the in-memory ref count and never rescheduled the alarm, so a short-lived lease could permanently bump the next alarm to now + keepAliveIntervalMs with nothing to pull it back. The dispose now recomputes the alarm from persistent state when the last lease is released (mirroring the facet release path), clearing the heartbeat when no other work needs it. Fixes #​1704 (root cause behind #​1703).

  • #​1724 c18a446 Thanks @​whoiskatrin! - Fix SQLite memory amplification in AgentSessionProvider.getHistory() and add byte-budgeted history reads (#​1710).

    The history path query previously selected m.* inside its recursive CTE, so every message blob was materialized in SQLite's recursion queue AND its ORDER BY sorter — 2-3 transient copies of the entire transcript inside the SQLite allocator, which in workerd shares the isolate's memory budget with the JS heap. On large media-heavy sessions this exhausted the allocator and surfaced as SQLITE_NOMEM on every wake. The CTE now recurses over (id, parent_id, depth) only and content is fetched separately in bounded chunks via json_each, which streams without materializing the result set. Leaf detection similarly no longer drags content blobs through its sorter.

    New session APIs for hosts that need to bound wake-time memory:

    • Session.getRecentHistory(maxContentBytes, minRecentMessages?) — returns the most recent messages on the active path that fit a byte budget (always at least the leaf, and at least minRecentMessages rows when provided — rows are individually capped at write time, so the floor keeps memory bounded), plus truncated and totalContentBytes. Backed by the optional SessionProvider.getRecentHistory(); falls back to a full read for providers that don't implement it, reporting the real serialized size and warning once that the budget cannot be enforced.
    • Session.getHistoryRowStats() — per-row stored sizes AND roles for the active path WITHOUT loading content (optional SessionProvider.getHistoryRowStats()), so oversized rows can be found and processed one at a time.
    • Session.internal_rewriteMessage() — maintenance write path that skips the full-history token-estimate status broadcast of a public updateMessage(), for framework passes (media eviction) that rewrite many rows with bounded memory.

    Bounded init reads: the init-time loaded-skill restore scan is now skipped entirely when no skill-capable context provider is configured, and when one is, it reads row stats and fetches assistant messages ONE AT A TIME instead of materializing the full transcript (full-read fallback for providers without row stats). Content hydration chunks are additionally bounded by cumulative stored bytes (4MB), not just row count, removing the 50-near-cap-rows worst case.

    Also adds chat:onstart:degraded, chat:hydration:windowed, and chat:media:evicted observability event types emitted by @cloudflare/think.

  • #​1748 4ec3b07 Thanks @​threepointone! - Ignore RPC responses when the WebSocket has already closed.

    Async callable methods can finish after a client disconnects. The server now treats that closed-socket response delivery as a no-op instead of surfacing an uncaught WebSocket send() after close() error from the Workers runtime.

  • #​1712 835e7b0 Thanks @​threepointone! - Reclaim resumable-stream buffers from an alarm so idle chats don't leak storage (#​1706)

    Resumable-stream chunk buffers (cf_ai_chat_stream_*) were only swept lazily when a subsequent stream completed. A chat that received a single turn and then went idle never triggered that sweep, so its buffers lingered in the Durable Object's SQLite for the lifetime of the DO.

    AIChatAgent and Think now arm a scheduled cleanup alarm whenever a stream starts and whenever it finishes (completes or errors). Arming on start guarantees that a stream whose DO is evicted mid-flight and never reaches a finish still gets a future sweep instead of leaking. This is the safety net for the non-durable path (e.g. chatRecovery: false, the AIChatAgent default): those turns don't run inside runFiber, so there's no leftover keepAlive alarm and no fiber-recovery scan, and if the client never reconnects nothing else wakes the DO. (Durable runFiber turns already self-heal — the keepAlive alarm survives eviction, wakes the DO, and recovery finalizes the stream, which arms cleanup — so arming on start is belt-and-suspenders there.) The alarm sweeps aged buffers via the retention windows below and re-arms only while reclaimable rows remain, so a fully-swept DO stops waking itself. Arming is idempotent so high-turn-count chats never accumulate cleanup schedules; the in-callback re-arm uses a fresh (non-idempotent) row so it survives the one-shot deletion of the firing schedule. No per-turn Durable Object and no change to the session DO lifecycle are required.

    Retention is now split into two short, purpose-specific windows instead of a single 24h threshold: completed/errored buffers are kept for a brief 10-minute reconnect-and-replay grace (the assistant message is persisted separately, so the buffer is only needed to replay a just-finished stream or deliver a terminal error frame to a reconnecting client), while abandoned in-flight (streaming) rows are kept for 1 hour so an interrupted turn has ample time to be resumed or recovered before its buffer is presumed dead. The abandoned-row sweep keys off last chunk activity rather than stream start time, so a long-running stream that is still emitting chunks is never reclaimed mid-flight.

    ResumableStream gains cleanup(now?) (force a sweep, bypassing the lazy interval gate) and hasReclaimableStreams() to support alarm-driven cleanup.

  • #​1713 18c438b Thanks @​threepointone! - Support client tools on the Think sub-agent chat() RPC path (#​1709)

    ChatOptions now accepts clientTools (the same ClientToolSchema[] carried over the WebSocket chat protocol) and an onClientToolCall executor. This lets a parent agent that drives a Think sub-agent over chat() expose client-defined tools to the sub-agent and complete the tool round trip within the same turn:

    await child.chat(message, callback, {
      signal,
      clientTools: [
        { name: "get_user_timezone", parameters: { type: "object" } },
      ],
      onClientToolCall: async ({ toolName, input }) =>
        runClientTool(toolName, input),
    });

    Without onClientToolCall, the schemas are still registered and the model's call is surfaced through the stream callback (execute-less), matching the WebSocket behavior. With it, the call is resolved inline so the turn can continue to completion — the RPC stream callback has no inbound result channel of its own.

    Unlike the WebSocket path, the schemas and executor are kept per-turn and are NOT persisted: the executor is a live RPC reference that cannot survive an eviction, and there is no SPA to replay a tool-result. This keeps chat recovery correct — an eviction-interrupted client-tool call is repaired like a server tool (the model proceeds) rather than being mistaken for a pending human interaction and parking forever.

    agents/chat's createToolsFromClientSchemas gains an optional { execute } delegate (and exports a new ClientToolExecutor type) to build the executable variant. Both additions are backward-compatible.

  • Updated dependencies [b2b6762, 4c2d1a7, 4c2d1a7]:

v0.15.0

Compare Source

Minor Changes
  • #​1701 6caa6e8 Thanks @​mattzcarey! - Refactor WorkerTransport to extend the official MCP SDK's WebStandardStreamableHTTPServerTransport instead of being a hand-rolled implementation.

    The wrapper is now a thin subclass that layers Workers-specific concerns on top of the SDK transport:

    • CORS — preflight handling and response-header injection (corsOptions).
    • Persistent transport state across DO hibernation via the existing MCPStorageApi adapter. sessionId, initialized, and initializeParams are snapshotted after each request and replayed on cold start so client capabilities are restored without a fresh initialize round-trip.
    • SSE keepalive — preserves the issue #​1583 fix. Uses the shared KEEPALIVE_FRAME (: keepalive\n\n) at KEEPALIVE_INTERVAL_MS (25s) from sse-keepalive.ts. Keepalive is unconditional on POST response streams and disabled on the standalone GET stream when an eventStore is configured (clients recover idle drops via Last-Event-ID instead).

    Everything else — session validation, SSE streaming, protocol-version negotiation, event-store resumability, send/close lifecycle — is delegated to the SDK transport. Net: ~500 fewer lines of code to maintain.

    The exported shape is unchanged: WorkerTransport, WorkerTransportOptions, MCPStorageApi, and TransportState keep the same names, and WorkerTransportOptions now also extends the SDK's transport options. The default createMcpHandler path (a fresh transport per request) is unaffected.

    There are, however, a few observable behaviour changes for callers who used WorkerTransport directly or relied on its previous quirks:

    • handleRequest's second argument is now { parsedBody?, authInfo? } (the SDK shape) instead of a positional parsedBody. createMcpHandler and McpAgent don't pass it, but callers invoking transport.handleRequest(request, parsedBody) directly must wrap it as transport.handleRequest(request, { parsedBody }).
    • retryInterval priming now follows the SDK contract. Previously a retry: priming frame was written to any GET SSE stream whenever retryInterval was set. The SDK only writes a priming event when an eventStore is configured and the negotiated protocol version is >= 2025-11-25 (older clients can't parse the empty-data: priming frame), and on POST streams rather than the standalone GET stream. retryInterval is still accepted but only affects that SDK priming event.
    • onerror now fires on client/protocol validation failures. The SDK invokes onerror for responses such as 400/405/406/415 and session-not-found. The old transport only surfaced internal errors, so handlers that log onerror will now see normal client mistakes.
    • onsessionclosed fires before the underlying close() (and therefore before onclose) on DELETE, instead of after. Ordering only; the session id is still passed.
    • started is now read-only. It was a writable instance field and is now a getter backed by the SDK's internal _started flag. Reading it (e.g. createMcpHandler's reconnect guard) is unchanged; assigning to it is no longer supported.
    • createMcpHandler now forwards SDK transport options. Because WorkerTransportOptions extends the SDK options, the handler passes through everything except its own route/authContext/transport fields — including eventStore, retryInterval, onsessionclosed, and the SDK DNS-rebinding options (enableDnsRebindingProtection, allowedHosts, allowedOrigins). The previous handler silently dropped these.

    The SDK dependency is pinned exactly (@modelcontextprotocol/sdk 1.29.0, no caret) because the wrapper relies on a handful of SDK internals for state restore and keepalive cleanup. The exact pin stops a patch release from shifting those out from under us, and the tests assert against the SDK field names so a bump fails CI loudly rather than breaking at runtime.

v0.14.5

Compare Source

Patch Changes
  • #​1613 124a47a Thanks @​threepointone! - Introduce the first Think framework layer for convention-driven agent apps.

    This release adds a manifest-driven Vite plugin that discovers agents from the
    agents/ directory, generates a Worker entrypoint and virtual framework
    modules, derives stable Durable Object class names, and merges framework-owned
    Worker config defaults with user Wrangler config. It also keeps the Think Vite
    plugin usable directly in normal Vite plugin arrays.

    The framework now supports optional app server entries, manifest-scoped friendly
    agent and sub-agent routing, deterministic route surfaces, colocated skill
    detection, Worker Loader requirement diagnostics, and explicit diagnostics for
    unsupported nested sub-agent conventions. Think currently supports top-level
    agents and one sub-agent layer; deeper nesting is rejected with guidance so that
    the routing and lifecycle model can be designed deliberately.

    This framework layer is experimental: both the Vite plugin (once, on build
    start) and the think CLI (on startup) emit a notice that the API may change
    or be removed in any release. The core Think agent runtime is unchanged.

    The Think CLI now includes think init, think inspect, and think types.
    think init scaffolds a minimal Workers/Vite Think app, safely handles prompted
    or named target directories, refuses unsafe migrations, and installs npm
    dependencies by default. think inspect exposes manifest/config diagnostics in
    text or JSON, while think types generates Think-owned declarations and can
    optionally compose with Wrangler type generation.

    This release also adds host-framework coverage for React Router and TanStack
    Start, updates examples to use the convention-first framework shape, and hardens
    Agents/worker-bundler virtual modules for bundled skill compatibility.

  • #​1613 124a47a Thanks @​threepointone! - Compile skill scripts ahead of time and remove the in-Worker bundler (drops ~14MB of esbuild-wasm from Worker bundles).

    Skill scripts are now always compiled to self-contained JavaScript before they run, and the runtime no longer ships an in-Worker bundler (@cloudflare/worker-bundler is no longer a dependency of agents):

    • The Agents Vite plugin compiles bundled skill scripts (scripts/*.ts/.tsx/.js/.mjs) with esbuild at build time — resolving sibling imports and stripping TypeScript — and marks them precompiled.
    • Skills served from R2 or other dynamic sources must be compiled before upload. A new compileSkillScript helper is exported from agents/skills/compile for use in your publish/upload tooling.
    • At runtime, a skill script that still needs compiling (raw TypeScript or a multi-file skill that wasn't bundled) throws a clear "must be compiled to a self-contained JavaScript module" error instead of silently bundling in-Worker.

    Breaking: if you ship raw TypeScript or multi-file skill scripts to R2 (or another dynamic source) and relied on the in-Worker bundler to compile them at runtime, bundle them ahead of time (e.g. with compileSkillScript) before upload. Bundled skills handled by the Vite plugin require no changes. The previously-added stubWorkerBundler option has been removed (there is nothing left to stub).

v0.14.4

Compare Source

Patch Changes
  • #​1693 6496c80 Thanks @​threepointone! - Fix AIChatAgent orphaned-stream recovery merging a new assistant turn into the previous assistant message (#​1691).

    When a stream was interrupted before its final assistant message was persisted (Durable Object hibernation, deploy churn, isolate restart, reconnect), orphan recovery reconstructed the message from stored chunks. If those chunks carried no provider start.messageId — the common case — recovery fell back to the last assistant message in history. That is correct for a continuation, but wrong for a normal new turn after a later user message: the recovered chunks for the new turn were appended onto the previous assistant message, corrupting both the persisted transcript and future model context.

    The assistant message id allocated when a stream starts is now persisted in the resumable-stream metadata (ResumableStream.start() records message_id). When the reconstructed chunks carry no provider start.messageId — the common case, and the one that triggered the bug — orphan recovery now uses this stored id instead of the last-assistant fallback, so a new turn becomes its own message and a continuation still merges into the message it was extending (it stored the cloned last-assistant id). A provider start.messageId, when present, still wins, matching the live path which adopts it for new turns. Stream rows written before this release have no stored id and keep the previous behavior (provider id if present, otherwise the last assistant message). The metadata migration adds a single column, guarded by a schema check so it runs only once.

    This also fixes two related variants of the same corruption on the durable (chatRecovery) continuation path:

    • When a stream was persisted early (e.g. at a tool-approval pause) and then recovered, the merge re-appended chunks it had already stored, leaving two parts for the same tool call. Recovery now skips reconstructed parts whose toolCallId already exists on the message.
    • When a new turn was interrupted before any assistant part was persisted — either because it was cut off in the window before the first chunk materialized, or because onChatRecovery returned { persist: false } — recovery would "continue" it by cloning the previous assistant message, merging the new turn into it. Recovery now detects that the conversation leaf is still the user message (no partial to continue) and re-runs the turn fresh, so it becomes its own message.

    @cloudflare/think is unaffected — its session-tree recovery already allocates a distinct message id per orphan and never falls back to the last assistant message.

v0.14.3

Compare Source

Patch Changes
  • #​1686 1e49880 Thanks @​threepointone! - Batch and pack chat-persistence SQLite writes to reduce rows written and round-trips.
    • agents: ResumableStream now packs each buffered group of stream chunks into a single SQLite row (a JSON array of chunk bodies) instead of writing one row per chunk. Single-chunk and large-chunk segments are stored unwrapped, and a per-segment byte cap keeps rows within the 2 MB SQLite row limit. This cuts chunk rows written / stored / scanned-on-replay by up to ~10×. Reads (replay, orphan reconstruction, getStreamChunks) transparently unpack both packed segments and legacy per-chunk rows, so existing stored data keeps working. Adds shared buildInClauseStrings and MAX_BOUND_PARAMS helpers exported from agents/chat.
    • @cloudflare/ai-chat: message cleanup (stale-row pruning and maxPersistedMessages enforcement) previously issued one DELETE per row in a loop; it now deletes rows in batched DELETE ... WHERE id IN (...) queries (capped at 100 bound parameters per query).
    • @cloudflare/think: deleteSubmissions() cleanup previously issued one DELETE per terminal submission (up to 500 per call); it now deletes rows in batched DELETE ... WHERE submission_id IN (...) queries.
    • @cloudflare/ai-chat & @cloudflare/think: chat-recovery incident TTL sweep previously deleted each stale incident with a separate awaited storage.delete(key) (which also defeats Durable Object write-coalescing); it now deletes incidents in batched storage.delete(keys) calls (up to 128 keys per call).

v0.14.2

Compare Source

Patch Changes
  • #​1684 ab6dd95 Thanks @​threepointone! - warn when chatRecovery is configured in onStart() (applied too late for wake recovery)

    On every Durable Object wake the SDK evaluates chat-recovery budgets — and may seal an interrupted turn, firing onExhaustedbefore the user's onStart() runs (_checkRunFibers() is ordered ahead of onStart()). A chatRecovery config produced inside onStart() is therefore read as the built-in defaults at the moment recovery decides, so a configured maxRecoveryWork / shouldKeepRecovering / onExhausted silently never applies to the recovery that matters.

    This is now documented on ChatRecoveryConfig and the chatRecovery fields of Think / AIChatAgent, and the SDK logs a one-time warning if it detects chatRecovery being reassigned during onStart(). The warning fires both for a custom config object and for chatRecovery = true (enabling recovery / its defaults too late); assigning false (disabling) in onStart() is intentionally not warned, since recovery already ran with the pre-onStart() value and disabling it afterward is a benign no-op for that wake. The fix is to assign chatRecovery as a class field or in the constructor.

  • #​1672 f96a2ba Thanks @​threepointone! - fix(chat-recovery): a turn making forward progress now survives unbounded deploy churn; add a work budget + shouldKeepRecovering runaway guard

    Durable chat recovery used to bound a single incident with a non-resetting 15-minute wall-clock ceiling (CHAT_RECOVERY_MAX_WINDOW_MS). That ceiling was overloaded — it served as both a recovery-duration bound and a runaway-loop guard — and it terminated healthy, actively-progressing turns that simply took longer than 15 minutes of wall-clock to finish while being repeatedly interrupted by a dense deploy window, sealing them with reason="max_recovery_window_exceeded" and discarding completed work.

    The two jobs are now decoupled (see design/rfc-chat-recovery-work-budget.md):

    • Duration is no longer a bound for a progressing turn. The non-resetting wall-clock ceiling is removed. A turn that keeps producing content survives unbounded deploy churn. Stuck turns are still sealed by the no-progress window (5 min, resets on progress); tight no-progress alarm loops by the attempt cap.
    • New runaway-loop guard, keyed to work, not time. The existing durable, monotonic, reconnect-immune progress counter is reused as a work meter. chatRecovery.maxRecoveryWork caps the produced content/tool units since an incident opened; exceeding it seals with reason="work_budget_exceeded". Defaults to Infinity — the SDK ships the mechanism but imposes no implicit cap, so it never terminates a progressing turn on its own.
    • New caller predicate. chatRecovery.shouldKeepRecovering(ctx) is consulted per recovery attempt from the second onward (only when no hard bound has already sealed the incident); returning false seals with reason="recovery_aborted". This is where integrators express token/cost/step budgets the SDK should not hardcode. A throwing predicate is logged and treated as "keep recovering".
    • The no-progress timeout is now configurable. chatRecovery.noProgressTimeoutMs (default 5 min, resets on progress) is the primary stuck-turn bound, now overridable per agent instead of a hardcoded constant.

    New public types from agents/chat: ChatRecoveryProgressContext. New ChatRecoveryConfig fields: maxRecoveryWork, shouldKeepRecovering, noProgressTimeoutMs. ChatRecoveryExhaustedContext.reason gains work_budget_exceeded and recovery_aborted; max_recovery_window_exceeded is retained as an open-string value but is no longer emitted.

    Both @cloudflare/ai-chat and @cloudflare/think (which carries its own copy of the recovery engine) are updated identically. Defaults are unchanged except that a progressing turn is no longer terminated by wall-clock age.

  • #​1668 d40cc8a Thanks @​ghostwriternr! - Fix RPC resource leaks in workflows.

    Workflows that use waitForApproval() or ThinkWorkflow.prompt() now release their RPC stubs promptly, preventing resource leaks and the associated "RPC stub was not disposed" warnings in your logs.

  • #​1679 c8d1d32 Thanks @​threepointone! - fix(sub-agents): a facet sub-agent no longer touches the root DO's WebSockets, fixing a production-only "Cannot perform I/O on behalf of a different Durable Object (Native)" crash (#​1677)

    A sub-agent (facet) that called setState(), broadcast(), or otherwise enumerated connections — directly or indirectly via the internal _broadcastProtocol() — could crash in production with Cannot perform I/O on behalf of a different Durable Object. ... (I/O type: Native). It reproduced when the root Agent held a live (hibernatable) WebSocket connection and the child facet was freshly bootstrapped; it never reproduced in wrangler dev/miniflare, which made it hard to catch.

    Root cause: the Agent overrides of getConnections() and getConnection() fell through to super.getConnections() / super.getConnection() for facets too. On a facet, that resolves to the host/root DO's hibernatable WebSockets, and reading their attachments from the facet's I/O context is a cross-DO native I/O access that workerd aborts. setState() tripped it only incidentally, because _broadcastProtocol() enumerates connections to compute its exclude list before sending anything.

    Fix: a facet's client connections are all virtual (real sockets owned by the root and bridged in), so getConnections()/getConnection() now return only the facet's virtual sub-agent connections and never fall through to the host DO's sockets. Delivery of facet state updates to clients connected directly to the sub-agent is unchanged.

  • #​1670 5d64940 Thanks @​threepointone! - Fix: a deploy that interrupts an in-flight runAgentTool child no longer abandons the still-running child as interrupted.

    Parent recovery re-attaches to a still-running child and tails it to its real terminal. Previously that re-attach used a flat 120s wall-clock budget that was not reset by the child's forward progress, so a healthy child whose recovery legitimately ran longer than the budget was sealed interrupted (and its already-completed work re-run from scratch), even while it was actively streaming.

    The re-attach budget is now progress-keyed: it bounds how long the parent waits with no forward progress from the child (resetting on every forwarded chunk), so a genuinely hung/silent child still seals interrupted after one no-progress window and can never block recovery forever, while a healthy child that keeps streaming is followed through to terminal. The parent re-arms (opens a fresh tail) only when the child's stream closes cleanly while it is still advancing — i.e. a re-evicted-but-progressing child. A full no-progress window (the child went silent) seals no-progress immediately even if the child streamed earlier in that window; it no longer grants a bonus window. This is both the honest stall signal and what keeps at most one pending tail reader alive per re-attach (no per-cycle reader accumulation).

    @cloudflare/think and @cloudflare/ai-chat additionally finalize a child facet's own agent-tool run row as soon as its recovered turn settles — regardless of whether recovery took the continue path (_chatRecoveryContinue) or the pre-stream retry path (_chatRecoveryRetry) — so a re-attached parent collects the terminal result immediately instead of waiting out a full no-progress window after the child has already finished.

    This release also adds:

    • Typed interrupted cause. RunAgentToolResult, the agentTool() AgentToolFailure envelope, the onAgentToolFinish lifecycle result, and the agent-tool-event wire event (kind "interrupted") now carry a machine-readable reason (AgentToolInterruptedReason: "no-progress" | "window-exceeded" | "not-tailable" | "inspect-timeout" | "inspect-failed" | "recovery-deadline") and a childStillRunning boolean on interrupted results, so callers (and UIs) can branch on why a run was abandoned (and whether the child is still running) instead of pattern-matching the human-readable error prose. retryable stays coarse (always true for interrupted); refine with reason / childStillRunning. These fields are persisted (schema bump), so they survive a reconnect replay — a client that reconnects after an interrupt reconstructs the same reason / childStillRunning a live client saw, rather than undefined. The persisted cause is cleared when a soft interrupted row is later repaired to completed/error.
    • Configurable re-attach budgets. Two new public AgentStaticOptionsagentToolReattachNoProgressTimeoutMs (default 120000, the progress-keyed no-progress budget) and agentToolReattachMaxWindowMs (default Infinity — no implicit wall-clock cap) — let an Agent tune re-attach. The hard ceiling defaults to uncapped to mirror chat-recovery's maxRecoveryWork: Infinity: a re-attached parent follows a healthy, still-advancing child for as long as it makes progress — exactly as it would on the live (never-evicted) path — so it never abandons a long-running-but-healthy child that simply outlasts a fixed wall clock under deploy churn. A hung/silent child is bounded by the no-progress budget; a content-runaway is bounded uniformly (live and recovery) by the child's own maxRecoveryWork / shouldKeepRecovering. Integrators that want a hard wall-clock cap (and the window-exceeded child teardown it triggers) can set agentToolReattachMaxWindowMs to a finite value. Symmetrically, setting agentToolReattachNoProgressTimeoutMs to Infinity now means "never seal on no-progress" (a silent-but-alive child is followed until its stream closes or the hard ceiling fires) instead of silently skipping the wait — 0 remains the "don't wait, collect only an already-terminal child" sentinel.
    • Give-up teardown (ceiling only). When the parent gives up at the hard window-exceeded ceiling — where the child has had its full recovery window and is truly exhausted — it now cancels the child (childStillRunning: false) so it stops consuming a fiber / keep-alive. no-progress give-ups stay soft (childStillRunning: true): the child is left running so a re-issue can still re-attach and repair it if it self-heals, preserving the repair-on-re-issue path. In both @cloudflare/think and @cloudflare/ai-chat, cancelAgentToolRun also aborts an in-flight chat-recovery turn (not just the original in-isolate run) and releases live tails — Think sweeps its _submissionAbortControllers, ai-chat its request AbortRegistry (abortAllRequests) — so a torn-down child stops grinding instead of finishing an orphaned recovered turn.
  • #​1680 8f9500a Thanks @​threepointone! - Remove the now-redundant _suppressProtocolBroadcasts facet-bootstrap guard.

    This flag was added in #​1425 to stop _broadcastProtocol() from enumerating the
    parent DO's WebSockets during facet bootstrap (the cross-DO Native I/O crash,
    #​1410/#​1677). The proper fix in #​1679 makes getConnections()/broadcast()
    facet-safe at the source — on a facet they return only virtual sub-agent
    connections and route through the parent bridge, never touching the parent's own
    sockets. With that, suppressing broadcasts during bootstrap is unnecessary, and
    removing it also lets legitimate state sync run during the bootstrap window.

    The separate request/WebSocket/email native-handle clearing from #​1425 is
    retained, since #​1679 does not cover that vector.

  • #​1675 d915bc6 Thanks @​threepointone! - The skill runner now imports just-bash and @cloudflare/codemode statically instead of dynamically, and both have moved from optional peer dependencies to regular dependencies of agents. The dynamic imports were ineffective in bundled Workers (the bundler includes them eagerly regardless) and triggered INEFFECTIVE_DYNAMIC_IMPORT warnings when bundled alongside @cloudflare/think, which imports them statically. @cloudflare/think also now statically imports its internal ExtensionManager instead of dynamically, removing the third such warning.

  • #​1662 df6c0d6 Thanks @​threepointone! - Add opt-in recovery for mid-turn context-window overflow.

    Compaction only fires between turns (Session.compactAfter checks the threshold on appendMessage). A single long, tool-heavy turn grows the prompt step-by-step inside one streamText loop and can exceed the model's context window mid-turn, before the next pre-turn check — the provider then 400s ("prompt is too long" / context_length_exceeded) and the turn dies terminally. Think deliberately ships no provider-specific error matching, so it could neither detect nor recover from this.

    This adds opt-in, provider-agnostic recovery (all default off — no behavior change unless enabled), configured through a single contextOverflow property on Think:

    • classifyChatError(error, ctx) — the app maps a raw error (or the in-stream error string) to a ChatErrorClassification ("context_overflow" | "rate_limit" | "transient" | "fatal" | "unknown"). Same framework-owns-the-mechanism / app-owns-the-provider-knowledge split as tokenCounter. The classification is also threaded to onChatError/observers via ChatErrorContext.classification. The bundled, exported defaultContextOverflowClassifier covers the common providers (Anthropic, OpenAI, Google, Bedrock, …) for apps that do not need custom classification.
    • contextOverflow.reactive + contextOverflow.maxRetries — when a turn fails with a context_overflow the app classified, Think discards the truncated partial, runs session.compact(), and re-runs the turn (bounded) from the compacted history instead of dying. The partial is intentionally not persisted: the retry restarts the turn from scratch, so keeping the cut-off partial would orphan a half-finished assistant message beside the recovered answer (and duplicate any tool work the retry re-issues). A no-op compaction or a spent budget surfaces the overflow terminally through onChatError with classification: "context_overflow" — never a silent end, never an infinite loop. Wired

Note

PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate

renovate Bot commented Apr 19, 2026

Copy link
Copy Markdown
Author

⚠️ Artifact update problem

Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is.

♻ Renovate will retry this branch, including artifacts, only when one of the following happens:

  • any of the package files in this branch needs updating, or
  • the branch becomes conflicted, or
  • you click the rebase/retry checkbox if found above, or
  • you rename this PR's title to start with "rebase!" to trigger it manually

The artifact failure details are included below:

File name: package-lock.json
npm warn Unknown env config "store". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: mcpecrets@0.1.0
npm error Found: zod@3.25.76
npm error node_modules/zod
npm error   zod@"^3.24.2" from the root project
npm error   peer zod@"^3.25.76 || ^4.1.8" from ai@6.0.205
npm error   node_modules/ai
npm error     peer ai@"^6.0.0" from agents@0.16.0
npm error     node_modules/agents
npm error       agents@"^0.16.0" from the root project
npm error       1 more (@cloudflare/ai-chat)
npm error     peer ai@"^6.0.0" from @cloudflare/ai-chat@0.8.5
npm error     node_modules/@cloudflare/ai-chat
npm error       peerOptional @cloudflare/ai-chat@">=0.8.5 <1.0.0" from agents@0.16.0
npm error     2 more (@ai-sdk/react, chat)
npm error   3 more (@x402/core, @x402/evm, chat)
npm error
npm error Could not resolve dependency:
npm error peer zod@"^4.0.0" from agents@0.16.0
npm error node_modules/agents
npm error   agents@"^0.16.0" from the root project
npm error   peer agents@">=0.16.0 <1.0.0" from @cloudflare/ai-chat@0.8.5
npm error   node_modules/@cloudflare/ai-chat
npm error     peerOptional @cloudflare/ai-chat@">=0.8.5 <1.0.0" from agents@0.16.0
npm error
npm error Fix the upstream dependency conflict, or retry this command with --force or --legacy-peer-deps to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error /runner/cache/others/npm/_logs/2026-06-14T08_04_31_184Z-eresolve-report.txt
npm error A complete log of this run can be found in: /runner/cache/others/npm/_logs/2026-06-14T08_04_31_184Z-debug-0.log

@renovate renovate Bot force-pushed the renovate/agents-0.x branch from 75a0534 to b3ea404 Compare May 2, 2026 07:51
@renovate renovate Bot changed the title Update dependency agents to ^0.11.0 Update dependency agents to ^0.12.0 May 2, 2026
@renovate renovate Bot force-pushed the renovate/agents-0.x branch from b3ea404 to 5dec9e0 Compare May 23, 2026 07:14
@renovate renovate Bot changed the title Update dependency agents to ^0.12.0 Update dependency agents to ^0.13.0 May 23, 2026
@renovate renovate Bot force-pushed the renovate/agents-0.x branch from 5dec9e0 to 5316bbd Compare June 5, 2026 03:32
@renovate renovate Bot changed the title Update dependency agents to ^0.13.0 Update dependency agents to ^0.14.0 Jun 5, 2026
@renovate renovate Bot force-pushed the renovate/agents-0.x branch from 5316bbd to 6bb9bcf Compare June 12, 2026 15:11
@renovate renovate Bot changed the title Update dependency agents to ^0.14.0 Update dependency agents to ^0.15.0 Jun 12, 2026
@renovate renovate Bot force-pushed the renovate/agents-0.x branch from 6bb9bcf to af7137d Compare June 14, 2026 08:04
@renovate renovate Bot changed the title Update dependency agents to ^0.15.0 Update dependency agents to ^0.16.0 Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants